#include <iostream>
#include <atomic>
#include <chrono>
#include <mutex>

#ifndef _MSC_VER
#include <pthread.h>
#endif

/// 用std::atomic_flag实现的spin lock
class SpinLock final
{
public:
	SpinLock()
	{
		_flag.clear();

		// C++20 This macro is no longer needed and deprecated, 
		// since default constructor of std::atomic_flag initializes it to clear state.
		// _flag = ATOMIC_FLAG_INIT;
	}
	~SpinLock() = default;
	SpinLock(const SpinLock&) = delete;
	SpinLock(const SpinLock&&) = delete;

	void lock() noexcept
	{
		// https://en.cppreference.com/w/cpp/atomic/atomic_flag_test_and_set
		// Example A spinlock mutex can be implemented in userspace using an atomic_flag

		// 
		while (_flag.test_and_set(std::memory_order_acquire));
	}

	bool try_lock() noexcept
	{
		return !_flag.test_and_set(std::memory_order_acquire);
	}

	void unlock() noexcept
	{
		_flag.clear(std::memory_order_release);
	}
private:
	std::atomic_flag _flag;
};

#ifndef _MSC_VER
/// https://rigtorp.se/spinlock/
struct spinlock {
	std::atomic<bool> lock_ = { 0 };

	void lock() noexcept {
		for (;;) {
			// Optimistically assume the lock is free on the first try
			if (!lock_.exchange(true, std::memory_order_acquire)) {
				return;
			}
			// Wait for lock to be released without generating cache misses
			while (lock_.load(std::memory_order_relaxed)) {
				// Issue X86 PAUSE or ARM YIELD instruction to reduce contention between
				// hyper-threads
				__builtin_ia32_pause();
			}
		}
	}

	bool try_lock() noexcept {
		// First do a relaxed load to check if lock is free in order to prevent
		// unnecessary cache misses if someone does while(!try_lock())
		return !lock_.load(std::memory_order_relaxed) &&
			!lock_.exchange(true, std::memory_order_acquire);
	}

	void unlock() noexcept {
		lock_.store(false, std::memory_order_release);
	}
};
#endif

int main()
{
	const int ts = 10000000;

	int ii1 = 0;
	int ii2 = 0;
	int ii3 = 0;

	std::atomic<int> i1(0);
	std::atomic<int> i2(0);
	std::atomic<int> i3(0);

	std::chrono::steady_clock::time_point beg;
	std::chrono::steady_clock::time_point end;

	beg = std::chrono::steady_clock::now();
	for (int i = 0; i < ts; i++)
	{
		ii1 += i / 2;
		ii2 += 1;
		ii3 += ii1;
	}
	end = std::chrono::steady_clock::now();
	std::cout << "run int      " << ts << " time cost (ms) = "
		<< std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count() << std::endl;

	beg = std::chrono::steady_clock::now();
	for (int i = 0; i < ts; i++)
	{
		i1 += i / 2;
		i2 += 1;
		i3 += i1;
	}
	end = std::chrono::steady_clock::now();
	std::cout << "run atomic   " << ts << " time cost (ms) = "
		<< std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count() << std::endl;

	ii1 = 0;
	ii2 = 0;
	ii3 = 0;
	std::mutex m;
	beg = std::chrono::steady_clock::now();
	for (int i = 0; i < ts; i++)
	{
		m.lock();
		ii1 += i / 2;
		ii2 += 1;
		ii3 += ii1;
		m.unlock();
	}
	end = std::chrono::steady_clock::now();
	std::cout << "run mutex    " << ts << " time cost (ms) = "
		<< std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count() << std::endl;

	ii1 = 0;
	ii2 = 0;
	ii3 = 0;
	SpinLock l;
	beg = std::chrono::steady_clock::now();
	for (int i = 0; i < ts; i++)
	{
		l.lock();
		ii1 += i / 2;
		ii2 += 1;
		ii3 += ii1;
		l.unlock();
	}
	end = std::chrono::steady_clock::now();
	std::cout << "run SpinLock " << ts << " time cost (ms) = "
		<< std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count() << std::endl;

#ifndef _MSC_VER
	ii1 = 0;
	ii2 = 0;
	ii3 = 0;
	spinlock ll;
	beg = std::chrono::steady_clock::now();
	for (int i = 0; i < ts; i++)
	{
		ll.lock();
		ii1 += i / 2;
		ii2 += 1;
		ii3 += ii1;
		ll.unlock();
	}
	end = std::chrono::steady_clock::now();
	std::cout << "run spinlock " << ts << " time cost (ms) = "
		<< std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count() << std::endl;

	// using pthread_spin_lock
	// https://docs.oracle.com/cd/E26502_01/html/E35303/ggecq.html
	ii1 = 0;
	ii2 = 0;
	ii3 = 0;
	pthread_spinlock_t lll;
	int pshared;
	int ret;

	/* initialize a spin lock */
	ret = pthread_spin_init(&lll, pshared);
	beg = std::chrono::steady_clock::now();
	for (int i = 0; i < ts; i++)
	{
		pthread_spin_lock(&lll);
		ii1 += i / 2;
		ii2 += 1;
		ii3 += ii1;
		pthread_spin_unlock(&lll);
	}
	end = std::chrono::steady_clock::now();
	std::cout << "run pthread_spinlock_t " << ts << " time cost (ms) = "
		<< std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count() << std::endl;
#endif

	return 0;
}
